<?php
/**
 * ConvertData interface for Convert data function
 * @author Nguyen Minh The
 */
interface IConvertData 
{
	public function Convert(&$arrayData, $xmlFilePath, $xmlRoot="root", $encType="UTF-8");
}

/**
 * ConvertData class for Convert data function
 * @author Nguyen Minh The
 * Created date: Apr-01 2008
 * Last updated date: Apr-03 2008
 *
 */
class ConvertData implements IConvertData  
{
	const ConvertTYPE_ARRAY2XML = "ARRAY2XML";
	const ConvertTYPE_XML2ARRAY = "XML2ARRAY";

    private $data;
    private $dom_tree;
	private $convertType;
	private $errorMsg;
	private $convertedData;
	private $encType;
	private $xmlRoot;
	
	/**
	 * This is init function for ConvertData class
	 *
	 * @param $convertTYPE is convertation type, must be either ConvertData::ConvertTYPE_ARRAY2XML or ConvertData::ConvertTYPE_XML2ARRAY
	 */
	function __construct($convertTYPE=ConvertData::ConvertTYPE_ARRAY2XML /* ConvertData::ConvertTYPE_ARRAY2XML | ConvertData::ConvertTYPE_XML2ARRAY */) 
	{
		$this->convertType	= $convertTYPE ;
	}

	/**
	 * Function for getting error message when FTP function return false value.
	 * 
	 * @return Return a error message
	 */
	public function ErrorMessage() {return $this->errorMsg;}

	/**
	 * Function for getting the converted data after running Convert function.
	 * 
	 * @return Return a error message
	 */
	public function ConvertedData() {return $this->convertedData;}

	/**
	 * This function use for Convert date from ConvertData object
	 *
	 * @param $arrayData is array data want to convert to xml or return array data when converting from xml to array. 
	 * @param $xmlFilePath is xml file path need to convert to array or xml file path contains converted data from array.
	 * @param $xmlRoot is root key in xml file's content and only is optional param, only has value when converting from array to xml
	 * @param $encType is encoding string and only is optional param, only has value when converting from array to xml
	 * @return return true if convert successful, return false if convert unsuccessful.
	 */
	public function Convert(&$arrayData, $xmlFilePath, $xmlRoot="root", $encType="UTF-8") 
	{
		$this->encType = $encType;
		$this->xmlRoot = $xmlRoot;
		
		if ($this->convertType==null || $this->convertType=="")
				$this->raiseError("Convert type is invalid, only is ConvertTYPE_ARRAY2XML or ConvertTYPE_XML2ARRAY.");
		else 
		{
			if ($this->convertType==ConvertData::ConvertTYPE_ARRAY2XML)
				$this->convertARRAY2XML($arrayData, $xmlFilePath);
			if ($this->convertType==ConvertData::ConvertTYPE_XML2ARRAY)
				$this->convertXML2ARRAY($arrayData, $xmlFilePath);
		}
		return ($this->errorMsg=="");
	}

	/**
	 * This is private function, called when Convert type is converting from array to xml
	 *
	 * @param $arrayData is array data need to convert to xml file
	 * @param $xmlFilePath is xml file path contains converted data
	 * @return return true if convert successful, return false if convert unsuccessful.
	 */
	private function convertARRAY2XML($arrayData, $xmlFilePath) 
	{
		$xmlArrayData = array();
		if ($this->xmlRoot==null || $this->xmlRoot=="") {
			foreach($arrayData as $key=>$value){
				$this->xmlRoot = $key;
				$xmlArrayData = $value;
				break;
			}
		} else $xmlArrayData = $arrayData;
		if(!is_array($xmlArrayData))
			$this->raiseError('Converted data requires an array');
		else if(!count($xmlArrayData))
			$this->raiseError('Array is empty');
		else {
			try {
				$this->data = new DOMDocument('1.0', $this->encType);
				$this->dom_tree = $this->data->createElement($this->xmlRoot);
				$this->data->appendChild($this->dom_tree);
				$this->recurseNode($xmlArrayData, $this->dom_tree);

				$file_handle = fopen($xmlFilePath,'w+');
				if (!$file_handle) $this->raiseError("Didn't permit to create $xmlFilePath.");
				else {
					$this->convertedData = $this->data->saveXML();
					fwrite($file_handle, $this->convertedData);
					fclose($file_handle);
				}
			} catch (Exception $ex) {
				$this->raiseError($ex);
			}
		}
		return ($this->errorMsg=="");
	}
	
	/**
	 * This function use for recursing a nested array and return dom back
	 *
	 * @param $data is array data
	 * @param $obj is dom element
	 */
	private function recurseNode($data, $obj){
		$i = 0;
		foreach($data as $key=>$value){
			if(is_array($value)){
				//recurse if neccisary
				$sub_obj[$i] = $this->data->createElement($key);
				$obj->appendChild($sub_obj[$i]);
				$this->recurseNode($value, $sub_obj[$i]);
			} elseif(is_object($value)) {
				//no object support so just say what it is
				$sub_obj[$i] = $this->data->createElement($key, 'Object: "' . $key . '" type: "'  . get_class($value) . '"');
				$obj->appendChild($sub_obj[$i]);
			} else {
				//straight up data, no weirdness
				$sub_obj[$i] = $this->data->createElement($key, $value);
				$obj->appendChild($sub_obj[$i]);
			}
			$i++;
		}
	}
		
	/**
	 * This is private function, called when Convert type is converting from xml to array
	 *
	 * @param $arrayData is array data need to contain converted data
	 * @param $xmlFilePath is xml path file need to convert content to array data
	 * @return return true if convert successful, return false if convert unsuccessful.
	 */
	private function convertXML2ARRAY(&$arrayData, $xmlFilePath) 
	{
		
		if (file_exists($xmlFilePath)) {
			$handle = fopen($xmlFilePath, "r");
			$contents = fread($handle, filesize($xmlFilePath));
			fclose($handle);
		} else {
			$this->raiseError("File $xmlFilePath not found.");
			return false;
		}

		if(!$contents) {
			$this->convertedData = $arrayData = array();
			return true;
		}
	
		if(!function_exists('xml_parser_create')) {
			$this->raiseError("'xml_parser_create()' function not found!");
			return false;
		}

		$this->convertedData = $arrayData = $this->XMLToArray($contents);
		return true;
		
	}

	/**
	 * This is private function use for converting a xml content to array data
	 *
	 * @param $xml is a xml conetnt
	 * @return an array data
	 */
	private function xmlToArray($xml) {

		$parser = xml_parser_create($this->encType); // For Latin-1 charset
		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); // Dont mess with my cAsE sEtTings
		xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); // Dont bother with empty info
		xml_parse_into_struct($parser, $xml, $values);
		xml_parser_free($parser);

		$return = array(); // The returned array
		$stack = array(); // tmp array used for stacking
		foreach($values as $val) {
			if($val['type'] == "open") 
				array_push($stack, $val['tag']);
			elseif($val['type'] == "close") 
				array_pop($stack);
			elseif($val['type'] == "complete") {
				array_push($stack, $val['tag']);
				$this->setArrayValue($return, $stack, $val['value']);
				array_pop($stack);
			}//if-elseif
		}//foreach
		return $return;
	}

	/**
	 * Put value to array from $stack or $value param
	 *
	 * @param  $array is array data need to set value
	 * @param $stack is stacking array
	 * @param $value is value for setting
	 * @return an array data
	 */
	private function setArrayValue(&$array, $stack, $value) {
		if ($stack) {
			$key = array_shift($stack);
			$this->setArrayValue($array[$key], $stack, $value);
			return $array;
		} else $array = $value;
	}

	/**
	 * This is private function use for raise an error message of ConvertData object.
	 *
	 * @param $msg is a message need to raise.
	 */
	private function raiseError($msg) 
	{
		$this->errorMsg = $msg;
	}
}
?>